/*
 * Copyright (C) 1998, 2009  John Pritchard and the Alto Project Group.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */
package alto.io.u;

/**
 * <p> Handling code <code>application/x-www-form-urlencoded</code> </p>
 * 
 * @author John Pritchard (john@syntelos.org)
 * @since 1.2
 */
public abstract class Url {


    public final static String decode( String source){
        if (null == source)
            return null;
        else
            return decode( source, source.toCharArray(), 0, source.length());
    }
    public final static String decode( char[] source, int ofs, int many){
        if (null == source || 0 > ofs || 1 > many)
            return null;
        else
            return decode( null, source, ofs, many);
    }
    /**
     * @param origin Possibly non- null origin of the character array
     * source, can be returned if the character array source has
     * nothing to decode
     * @param source Must be non- null
     * @param ofs Must be valid
     * @param many Must be valid
     */
    public final static String decode( String origin, char[] source, int ofs, int many){
        Chbuf re = new Chbuf(many);
        char ch;
        for (int cc = ofs, len = (ofs+many); cc < len; cc++){
            ch = source[cc];
	    switch(ch){
	    case '+':
		re.append(' ');
		break;
	    case '%':
                cc += 1;
                if (cc < len && (cc+1) < len)
                    re.append( char_decode( source, cc));
                cc += 1;
		break;
	    default:
                re.append(ch);
		break;
	    }
        }
        if (null != origin && re.length() == origin.length())
            return origin;
        else
            return re.toString();
    }
    public final static String encode( String source){
        if (null == source)
            return null;
        else
            return encode( source, source.toCharArray(), 0, source.length());
    }
    public final static String encode( char[] source, int ofs, int many){
        if (null == source || 0 > ofs || 1 > many)
            return null;
        else
            return encode( null, source, ofs, many);
    }
    /**
     * @param origin Possibly non- null origin of the character array
     * source, can be returned if the character array source has
     * nothing to encode
     * @param source Must be non- null
     * @param ofs Must be valid
     * @param many Must be valid
     * @return Encode everything but the set of alpha numeric ASCII characters
     */
    public final static String encode( String origin, char[] source, int ofs, int many){
        Chbuf re = new Chbuf(many);
        char ch;
        char[] encbuf = new char[3];
        enc:
        for (int cc = ofs, len = (ofs+many); cc < len; cc++){
            ch = source[cc];
            switch(ch){
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case 'a':
            case 'b':
            case 'c':
            case 'd':
            case 'e':
            case 'f':
            case 'g':
            case 'h':
            case 'i':
            case 'j':
            case 'k':
            case 'l':
            case 'm':
            case 'n':
            case 'o':
            case 'p':
            case 'q':
            case 'r':
            case 's':
            case 't':
            case 'u':
            case 'v':
            case 'w':
            case 'x':
            case 'y':
            case 'z':
            case 'A':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
            case 'G':
            case 'H':
            case 'I':
            case 'J':
            case 'K':
            case 'L':
            case 'M':
            case 'N':
            case 'O':
            case 'P':
            case 'Q':
            case 'R':
            case 'S':
            case 'T':
            case 'U':
            case 'V':
            case 'W':
            case 'X':
            case 'Y':
            case 'Z':
            case '.':
            case '/':
            case '-':
            case '_':

                re.append(ch);
                continue enc;

            default:
                char_encode(ch,encbuf,0);
                re.append(encbuf,0,3);
                continue enc;
            }
        }
        if (null != origin && re.length() == origin.length())
            return origin;
        else
            return re.toString();
    }

    
    /**
     * Two ASCII hex digits into one character value
     * @param cary Character array source of two (ASCII) hex digits
     * must not be null.  Accepts <code>ofs</code> pointing to '%', or
     * to the first hex char.
     * @param ofs Offset into character array source for two (ASCII)
     * hex digits must be valid, length for this offset and one more
     * must be valid.
     */
    public final static char char_decode( char[] cary, int ofs){

        int many = ((null != cary)?(cary.length):(0));

        boolean fin = false;

        if ( 0 > ofs) 
            ofs = 0;

        char re = (char)0;

        int ch;
        parse:
        for ( int cc = ofs, len = many; cc < len; cc++){

            ch = cary[cc];
            switch(ch){
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                if (fin){
                    re += (char)(ch-'0');
                    return re;
                }
                else {
                    fin = true;
                    re += (char)((ch-'0')<<4);
                    continue parse;
                }
            case 'a':
            case 'b':
            case 'c':
            case 'd':
            case 'e':
            case 'f':
                if (fin){
                    re += (char)((ch-'a')+10);
                    return re;
                }
                else {
                    fin = true;
                    re += (char)(((ch-'a')+10)<<4);
                    continue parse;
                }
            case 'A':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
                if (fin){
                    re += (char)((ch-'A')+10);
                    return re;
                }
                else {
                    fin = true;
                    re += (char)(((ch-'A')+10)<<4);
                    continue parse;
                }
            case '%':
                continue parse;
            default:
                break parse;
            }
        }
        throw new IllegalArgumentException(String.valueOf(ofs));
    }

    private final static char[] hexchars = { '0', '1', '2', '3', '4', '5', '6', 
                                             '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

    /**
     * @param ch Character value to encode into <code>'%'</code> and two hex digits
     * @param target Character array for hex digits
     * @param ofs Offset into character array for three characters must
     * be valid, and array length for one more offset must be valid
     */
    public final static void char_encode( char ch, char[] target, int ofs){
        int value = ch;
        target[ofs++] = '%';
        target[ofs++] = hexchars[(value>>4)&0xf];
        target[ofs] = hexchars[(value&0xf)];
        return;
    }

    
    /**
     * <p> Parse query string into map. </p>
     */
    public final static Objmap querystring( String query){
        return querystring(null,query);
    }
    /**
     * <p> Parse query string into map. </p>
     */
    public final static Objmap querystring( Objmap dict, String query){
        if (null == query)
            return dict;
        else {
            char[] string = query.toCharArray();
            int string_len = string.length;
            if (1 > string_len)
                return dict;
            else {
                if (null == dict)
                    dict = new Objmap();
                //
                int idx = 0;
                String name = null, value = null;
                for (int scan = 0; scan < string_len; scan++){
                    switch(string[scan]){
                    case '&':
                        value = new java.lang.String(string,idx,(scan-idx));
                        idx = (scan+1);
                        dict.put(name,value);
                        name = null;
                        value = null;
                        break;
                    case '=':
                        name = new java.lang.String(string,idx,(scan-idx));
                        idx = (scan+1);
                        break;
                    default:
                        break;
                    }
                }
                /*
                 * Tail case
                 */
                if (0 < idx && null != name){
                    value = new java.lang.String(string,idx,(string_len-idx));
                    dict.put(name,value);
                }
                return dict;
            }
        }
    }
}
